Data from Cytation experiment performed 2020-12-14

Combined luminescence and imaging reads of several SCLC and melanoma cell lines treated with various drugs and concentrations.

NOTE: Some contamination (bacterial? fungal?) was visible in some cell culture vessels prior to experiment (per Clayton Wandishin).

library(diprate)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
closestTime <- function (mytime, timevec, direction = "", out = "pos")
{
    sapply(mytime, function(mt) 
    {
        difft <- switch(direction,
                        before = -difftime(timevec, mt),
                        after = -difftime(mt, timevec),
                        abs(difftime(mt, timevec))
                        )
        if(direction %in% c("before","after"))
        {
            mindiff = ifelse(length(difft[difft>=0]) == 0, NA, min(difft[difft>=0]))
            if(is.na(mindiff))
            {
                r <- NA
            } else {
                r <- as.vector(switch(out,
                                      time = timevec[which(difft == min(difft[difft>=0]) )],
                                      pos = which(difft == min(difft[difft>=0])),
                                      amt = min(difft[difft>=0])
                                      )
                               )
            }
        } else {
            r <- switch(out,
                        time = as.character(timevec[which(difft == min(difft))]),
                        pos = which(difft == min(difft)),
                        amt = min(difft)
                        )
        }
        return(r)
    })
}

getDateTime <- function(dname)
{
    o <- dname[grepl("[0-9]{6}_*[0-9]{6}",dname)]
    if(all(grepl("[0-9]{12}",o)))
    {
        mytime <- strptime(o, '%y%m%d%H%M%S')
    } else {
        mytime <- paste(sapply(strsplit(o,'_'), '[[', 1),
            sapply(strsplit(o,'_'), '[[', 2), sep='_')
        mytime <- strptime(mytime, '%y%m%d_%H%M%S')
    }
    names(mytime) <- NULL
    return(mytime)
}

Data assembly

Data were not completely compiled into a single dataframe for analysis. Must assemble prior to analysis.

TOPDIR <- "../data/20201216_Lum_Image_Run/"
# Momentum log path
ml_path <- file.path(TOPDIR,'MOMENTUM_LOGS_FOR_V004831B_20201214_100msRFP_10msGFP_20201216_153632_0.txt')
# Momentum log
ml <- read.csv(ml_path, header=FALSE, skip=5, as.is=TRUE, sep='\t')
# Index of rows with reads
read_i <- which(ml$V6=='Read')
# Keep only relevant columns
ml <- ml[read_i,c('V1','V6','V7','V12','V13','V14')]
# Rename columns
colnames(ml) <- c('instrument','action','id','start.time','end.time','barcode')
rownames(ml) <- NULL


lum <- read.csv(file.path(TOPDIR,'CombinedLuminescenceTOTAL.csv'), as.is=TRUE, row.names=1)
# Cell counts = cc
cc <- read.csv(file.path(TOPDIR,'Cellcounts_20201214.csv'), as.is=TRUE)
cc <- cc[order(cc$plate_id,cc$well),]

# standardize colnames
colnames(cc) <- gsub("_",".",colnames(cc))

Data munging

Luminescence data already has most annotation associated with it, including plate barcode (ID), cell line, drug, drug conc, time and well. The times were parsed from the filenames of the raw luminescence data (see [../data/20201216_Lum_Image_Run/MultiPlateMultiFileMergeLum20201216_special.py])

Momentum log should have times matching (or very similar to) these values. Must make sure they’re in the same format. Use the closestTime function to obtain the position in the

The cell counts were obtained from two different positions within each well. These values should be summed and same for the counts of nuclei that are positive in the second channel (the dead-cell stain; ch2_pos).

# Cell count image file path =  ccifp; from task arguments sent to py-seg image processing pipeline
ccifp <- read.csv(file.path(TOPDIR,'TaskArgs_20201214.csv'))$nuc_im_path
# Find times for each unique plate
ccifp <- gsub("/mnt/monica/quaranta2/Cytation/2020-12-14/images/","",ccifp)
ccifp <- unique(sapply(strsplit(ccifp, "/"), "[[", 1))

# get plate IDs from number after "Experiment"; should match Momentum log
pid <- as.integer(sapply(strsplit(ccifp,"Experiment"), "[[", 2))
names(pid) <- getDateTime(ccifp)

cc$image.time <- names(pid)[match(cc$plate.id,pid)]

# standardize lum colnames
colnames(lum) <- gsub("_",".",tolower(colnames(lum)))
if(!any(grepl("drug1",colnames(lum)))) colnames(lum) <- gsub("drug","drug1",colnames(lum))
colnames(lum)[colnames(lum)=="tothour.lum"] <- "time"
colnames(lum)[colnames(lum)=="datetime.lum"] <- "lum.time"

# ulfst = unique lum file save times
ulfst <- unique(lum$lum.time)

# plate name (barcode from Momentum log)
# pn <- ml[closestTime(unique(cc$image.time),ml[grepl('RFP',ml$id),'start.time.rfmt']),'barcode']
# plate name (from lum)
pn <- lum[closestTime(unique(cc$image.time),unique(lum$lum.time),direction="before"),'plate.name']
names(pn) <- unique(cc$image.time)

# add plate.name (barcode) to cc
cc$plate.name <- pn[match(cc$image.time,names(pn))]

# must sum cell counts and ch2_pos per uid2 (unique for each time point) 
cc$uid <- paste(cc$plate.id,cc$well,sep="_")
cc$uid2 <- paste(cc$uid,cc$plate.id,sep="_")
cc_temp <- cc[!duplicated(cc$uid2),]
cc_temp$cell.count <- sapply(unique(cc$uid2), function(x) sum(cc[cc$uid2==x,'cell.count']))
cc_temp$ch2.pos <- sapply(unique(cc$uid2), function(x) sum(cc[cc$uid2==x,'ch2.pos']))

# time lookup table: cc image times as names, lum file save times as values
# time_lut <- closestTime(unique(cc_temp$image.time),ulfst,direction="before",out="time")

# Add luminescence read times; note that the first luminescence read is missing
# cc_temp$lum.time <- time_lut[cc_temp$image.time]
keep_cols <- c("plate.name","image.time","lum.time","well","cell.count","ch2.pos")
# remove unneeded columns from cc_temp
cc_temp <- cc_temp[,colnames(cc_temp) %in% keep_cols]
# first plate did not get luminescence read; must pull info from Momentum logs or remove
cc_temp <- cc_temp[!is.na(cc_temp$plate.name),]
# cc_temp$uid <- paste(cc_temp$plate.name,cc_temp$well,cc_temp$lum.time, sep="_")

lum$uid <- paste(lum$lum.time, lum$well, sep="_")
lum$pid <- paste(lum$plate.name, lum$lum.time, sep="_")

# keep only unique times for each luminescence read
lum_times <- lum[!duplicated(lum$pid),]
lum_times$plate.name <- factor(lum_times$plate.name, 
                               levels=unique(lum_times$plate.name)[order(lum_times[!duplicated(lum_times$plate.name),"time"])])
lum_times <- lum_times[order(lum_times$plate.name,lum_times$time),]

NOTE: plate names and times do not match between imaging and lum

Try to use momentum log to reconcile. This code likely not necessary since already have matched times in cc_temp)

ml$start.time.rfmt <- strptime(ml$start.time, format="%m/%d/%Y %r")
ml$end.time.rfmt <- strptime(ml$end.time, format="%m/%d/%Y %r")
ml$image.time <- unique(cc$image.time)[closestTime(ml$start.time.rfmt,unique(cc$image.time))]

# copy image.time from successive row
ml[grep("Lum",ml$id),"image.time"] <- ml[grep("Lum",ml$id)+1,"image.time"]

ml$lum.time <- unique(lum_times$lum.time)[closestTime(ml$end.time.rfmt,unique(lum_times$lum.time))]
# copy lum.time from preceding row
ml[grep("RFP",ml$id),"lum.time"] <- ml[grep("RFP",ml$id)-1,"lum.time"]
cc_temp$lum.time <- ml[match(cc_temp$image.time,ml$image.time),"lum.time"]
cc_temp$uid <- paste(cc_temp$lum.time, cc_temp$well, sep="_")
cc_temp <- cc_temp[match(lum$uid,cc_temp$uid),]
rownames(cc_temp) <- NULL

lum2 <- cbind(lum, cc_temp[,c('cell.count','ch2.pos','image.time')])

NOTE: first luminescence reading is missing

NOTE: in this dataset, luminescence values below ~2000 indistinguishable from background

Separate data by barcode (cell line)

Plate barcodes were saved as plate.name. Each barcode is associated with a different cell line.

by_plate <- lapply(unique(lum2$plate.name), function(pn) lum2[lum2$plate.name==pn,])
names(by_plate) <- unique(lum2$plate.name)
par(mfrow=c(4,3))
for (barcode in names(by_plate)) {
    cont <- by_plate[[barcode]][by_plate[[barcode]]$drug1.conc==0,]
    cont <- cont[order(cont$well,cont$time),]
    if(any(grepl("DMS53",cont$cell.line))) cont <- cont[cont$time <= 48,]
    cont <- cont[cont$time <= 96,]
    cl <- unique(cont$cell.line)
    #do.call(plotGC, getGCargs(cont[cont$well %in% c('B11','C11','D11'),], 
    #                          dat.col = c("time", "cell.count", "well")))
    invisible(do.call(plotGC, append(getGCargs(cont,
                              dat.col = c("time", "cell.count", "well")),list(main=paste(cl,"cell count doublings")))))
    invisible(do.call(plotGC, append(getGCargs(cont,
                              dat.col = c("time", "rlu", "well")),list(main=paste(cl,"lum doublings")))))
    invisible(do.call(plotGC, append(getGCargs(cont,
                              dat.col = c("time", "rlu", "well")),list(main=paste(cl,"lum"),count.type = "log"))))    
}

Clayton’s heuristic

Clayton Wandishin states that the relationship between luminscence and cell count can be explained by the following mathematical model:

1 = d/dx(log2(J*Lum))

par(mfrow=c(1,3))
invisible(lapply(names(j), function(n) {
    dat <- my_lum[my_lum$cell.line==n,]
    plot(dat$time,log2(dat$rlu)*dat$time/j[n])
}
))

LS0tCnRpdGxlOiAiUlQtR2xvIEN5dGF0aW9uIDIwMjAtMTAtMDggYW5hbHlzaXMiCmF1dGhvcjogIkRhcnJlbiBUeXNvbiIKZGF0ZTogIjAzLzEwLzIwMjEiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCiMjIyMgRGF0YSBmcm9tIEN5dGF0aW9uIGV4cGVyaW1lbnQgcGVyZm9ybWVkIDIwMjAtMTItMTQKQ29tYmluZWQgbHVtaW5lc2NlbmNlIGFuZCBpbWFnaW5nIHJlYWRzIG9mIHNldmVyYWwgU0NMQyBhbmQgbWVsYW5vbWEgY2VsbCBsaW5lcyB0cmVhdGVkIHdpdGggdmFyaW91cyBkcnVncyBhbmQgY29uY2VudHJhdGlvbnMuICAKCk5PVEU6IFNvbWUgY29udGFtaW5hdGlvbiAoYmFjdGVyaWFsPyBmdW5nYWw/KSB3YXMgdmlzaWJsZSBpbiBzb21lIGNlbGwgY3VsdHVyZSB2ZXNzZWxzIHByaW9yIHRvIGV4cGVyaW1lbnQgKHBlciBDbGF5dG9uIFdhbmRpc2hpbikuICAKCgpgYGB7ciBTZXR1cH0KbGlicmFyeShkaXByYXRlKQoKY2xvc2VzdFRpbWUgPC0gZnVuY3Rpb24gKG15dGltZSwgdGltZXZlYywgZGlyZWN0aW9uID0gIiIsIG91dCA9ICJwb3MiKQp7CiAgICBzYXBwbHkobXl0aW1lLCBmdW5jdGlvbihtdCkgCiAgICB7CiAgICAgICAgZGlmZnQgPC0gc3dpdGNoKGRpcmVjdGlvbiwKICAgICAgICAgICAgICAgICAgICAgICAgYmVmb3JlID0gLWRpZmZ0aW1lKHRpbWV2ZWMsIG10KSwKICAgICAgICAgICAgICAgICAgICAgICAgYWZ0ZXIgPSAtZGlmZnRpbWUobXQsIHRpbWV2ZWMpLAogICAgICAgICAgICAgICAgICAgICAgICBhYnMoZGlmZnRpbWUobXQsIHRpbWV2ZWMpKQogICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgaWYoZGlyZWN0aW9uICVpbiUgYygiYmVmb3JlIiwiYWZ0ZXIiKSkKICAgICAgICB7CiAgICAgICAgICAgIG1pbmRpZmYgPSBpZmVsc2UobGVuZ3RoKGRpZmZ0W2RpZmZ0Pj0wXSkgPT0gMCwgTkEsIG1pbihkaWZmdFtkaWZmdD49MF0pKQogICAgICAgICAgICBpZihpcy5uYShtaW5kaWZmKSkKICAgICAgICAgICAgewogICAgICAgICAgICAgICAgciA8LSBOQQogICAgICAgICAgICB9IGVsc2UgewogICAgICAgICAgICAgICAgciA8LSBhcy52ZWN0b3Ioc3dpdGNoKG91dCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aW1lID0gdGltZXZlY1t3aGljaChkaWZmdCA9PSBtaW4oZGlmZnRbZGlmZnQ+PTBdKSApXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwb3MgPSB3aGljaChkaWZmdCA9PSBtaW4oZGlmZnRbZGlmZnQ+PTBdKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYW10ID0gbWluKGRpZmZ0W2RpZmZ0Pj0wXSkKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgICAgIH0KICAgICAgICB9IGVsc2UgewogICAgICAgICAgICByIDwtIHN3aXRjaChvdXQsCiAgICAgICAgICAgICAgICAgICAgICAgIHRpbWUgPSBhcy5jaGFyYWN0ZXIodGltZXZlY1t3aGljaChkaWZmdCA9PSBtaW4oZGlmZnQpKV0pLAogICAgICAgICAgICAgICAgICAgICAgICBwb3MgPSB3aGljaChkaWZmdCA9PSBtaW4oZGlmZnQpKSwKICAgICAgICAgICAgICAgICAgICAgICAgYW10ID0gbWluKGRpZmZ0KQogICAgICAgICAgICAgICAgICAgICAgICApCiAgICAgICAgfQogICAgICAgIHJldHVybihyKQogICAgfSkKfQoKZ2V0RGF0ZVRpbWUgPC0gZnVuY3Rpb24oZG5hbWUpCnsKICAgIG8gPC0gZG5hbWVbZ3JlcGwoIlswLTldezZ9XypbMC05XXs2fSIsZG5hbWUpXQogICAgaWYoYWxsKGdyZXBsKCJbMC05XXsxMn0iLG8pKSkKICAgIHsKICAgICAgICBteXRpbWUgPC0gc3RycHRpbWUobywgJyV5JW0lZCVIJU0lUycpCiAgICB9IGVsc2UgewogICAgICAgIG15dGltZSA8LSBwYXN0ZShzYXBwbHkoc3Ryc3BsaXQobywnXycpLCAnW1snLCAxKSwKICAgICAgICAgICAgc2FwcGx5KHN0cnNwbGl0KG8sJ18nKSwgJ1tbJywgMiksIHNlcD0nXycpCiAgICAgICAgbXl0aW1lIDwtIHN0cnB0aW1lKG15dGltZSwgJyV5JW0lZF8lSCVNJVMnKQogICAgfQogICAgbmFtZXMobXl0aW1lKSA8LSBOVUxMCiAgICByZXR1cm4obXl0aW1lKQp9CmBgYAoKIyMjIyBEYXRhIGFzc2VtYmx5CkRhdGEgd2VyZSBub3QgY29tcGxldGVseSBjb21waWxlZCBpbnRvIGEgc2luZ2xlIGRhdGFmcmFtZSBmb3IgYW5hbHlzaXMuIE11c3QgYXNzZW1ibGUgcHJpb3IgdG8gYW5hbHlzaXMuCgpgYGB7cn0KVE9QRElSIDwtICIuLi9kYXRhLzIwMjAxMjE2X0x1bV9JbWFnZV9SdW4vIgojIE1vbWVudHVtIGxvZyBwYXRoCm1sX3BhdGggPC0gZmlsZS5wYXRoKFRPUERJUiwnTU9NRU5UVU1fTE9HU19GT1JfVjAwNDgzMUJfMjAyMDEyMTRfMTAwbXNSRlBfMTBtc0dGUF8yMDIwMTIxNl8xNTM2MzJfMC50eHQnKQojIE1vbWVudHVtIGxvZwptbCA8LSByZWFkLmNzdihtbF9wYXRoLCBoZWFkZXI9RkFMU0UsIHNraXA9NSwgYXMuaXM9VFJVRSwgc2VwPSdcdCcpCiMgSW5kZXggb2Ygcm93cyB3aXRoIHJlYWRzCnJlYWRfaSA8LSB3aGljaChtbCRWNj09J1JlYWQnKQojIEtlZXAgb25seSByZWxldmFudCBjb2x1bW5zCm1sIDwtIG1sW3JlYWRfaSxjKCdWMScsJ1Y2JywnVjcnLCdWMTInLCdWMTMnLCdWMTQnKV0KIyBSZW5hbWUgY29sdW1ucwpjb2xuYW1lcyhtbCkgPC0gYygnaW5zdHJ1bWVudCcsJ2FjdGlvbicsJ2lkJywnc3RhcnQudGltZScsJ2VuZC50aW1lJywnYmFyY29kZScpCnJvd25hbWVzKG1sKSA8LSBOVUxMCgoKbHVtIDwtIHJlYWQuY3N2KGZpbGUucGF0aChUT1BESVIsJ0NvbWJpbmVkTHVtaW5lc2NlbmNlVE9UQUwuY3N2JyksIGFzLmlzPVRSVUUsIHJvdy5uYW1lcz0xKQojIENlbGwgY291bnRzID0gY2MKY2MgPC0gcmVhZC5jc3YoZmlsZS5wYXRoKFRPUERJUiwnQ2VsbGNvdW50c18yMDIwMTIxNC5jc3YnKSwgYXMuaXM9VFJVRSkKY2MgPC0gY2Nbb3JkZXIoY2MkcGxhdGVfaWQsY2Mkd2VsbCksXQoKIyBzdGFuZGFyZGl6ZSBjb2xuYW1lcwpjb2xuYW1lcyhjYykgPC0gZ3N1YigiXyIsIi4iLGNvbG5hbWVzKGNjKSkKYGBgCgojIyMjIERhdGEgbXVuZ2luZwpMdW1pbmVzY2VuY2UgZGF0YSBhbHJlYWR5IGhhcyBtb3N0IGFubm90YXRpb24gYXNzb2NpYXRlZCB3aXRoIGl0LCBpbmNsdWRpbmcgcGxhdGUgYmFyY29kZSAoSUQpLCBjZWxsIGxpbmUsIGRydWcsIGRydWcgY29uYywgdGltZSBhbmQgd2VsbC4gVGhlIHRpbWVzIHdlcmUgcGFyc2VkIGZyb20gdGhlIGZpbGVuYW1lcyBvZiB0aGUgcmF3IGx1bWluZXNjZW5jZSBkYXRhIChzZWUgWy4uL2RhdGEvMjAyMDEyMTZfTHVtX0ltYWdlX1J1bi9NdWx0aVBsYXRlTXVsdGlGaWxlTWVyZ2VMdW0yMDIwMTIxNl9zcGVjaWFsLnB5XSkgIAoKTW9tZW50dW0gbG9nIHNob3VsZCBoYXZlIHRpbWVzIG1hdGNoaW5nIChvciB2ZXJ5IHNpbWlsYXIgdG8pIHRoZXNlIHZhbHVlcy4gTXVzdCBtYWtlIHN1cmUgdGhleSdyZSBpbiB0aGUgc2FtZSBmb3JtYXQuIFVzZSB0aGUgYGNsb3Nlc3RUaW1lYCBmdW5jdGlvbiB0byBvYnRhaW4gdGhlIHBvc2l0aW9uIGluIHRoZSAKClRoZSBjZWxsIGNvdW50cyB3ZXJlIG9idGFpbmVkIGZyb20gdHdvIGRpZmZlcmVudCBwb3NpdGlvbnMgd2l0aGluIGVhY2ggd2VsbC4gVGhlc2UgdmFsdWVzIHNob3VsZCBiZSBzdW1tZWQgYW5kIHNhbWUgZm9yIHRoZSBjb3VudHMgb2YgbnVjbGVpIHRoYXQgYXJlIHBvc2l0aXZlIGluIHRoZSBzZWNvbmQgY2hhbm5lbCAodGhlIGRlYWQtY2VsbCBzdGFpbjsgYGNoMl9wb3NgKS4KCmBgYHtyIFRpbWUgbWF0Y2hpbmd9CiMgQ2VsbCBjb3VudCBpbWFnZSBmaWxlIHBhdGggPSAgY2NpZnA7IGZyb20gdGFzayBhcmd1bWVudHMgc2VudCB0byBweS1zZWcgaW1hZ2UgcHJvY2Vzc2luZyBwaXBlbGluZQpjY2lmcCA8LSByZWFkLmNzdihmaWxlLnBhdGgoVE9QRElSLCdUYXNrQXJnc18yMDIwMTIxNC5jc3YnKSkkbnVjX2ltX3BhdGgKIyBGaW5kIHRpbWVzIGZvciBlYWNoIHVuaXF1ZSBwbGF0ZQpjY2lmcCA8LSBnc3ViKCIvbW50L21vbmljYS9xdWFyYW50YTIvQ3l0YXRpb24vMjAyMC0xMi0xNC9pbWFnZXMvIiwiIixjY2lmcCkKY2NpZnAgPC0gdW5pcXVlKHNhcHBseShzdHJzcGxpdChjY2lmcCwgIi8iKSwgIltbIiwgMSkpCgojIGdldCBwbGF0ZSBJRHMgZnJvbSBudW1iZXIgYWZ0ZXIgIkV4cGVyaW1lbnQiOyBzaG91bGQgbWF0Y2ggTW9tZW50dW0gbG9nCnBpZCA8LSBhcy5pbnRlZ2VyKHNhcHBseShzdHJzcGxpdChjY2lmcCwiRXhwZXJpbWVudCIpLCAiW1siLCAyKSkKbmFtZXMocGlkKSA8LSBnZXREYXRlVGltZShjY2lmcCkKCmNjJGltYWdlLnRpbWUgPC0gbmFtZXMocGlkKVttYXRjaChjYyRwbGF0ZS5pZCxwaWQpXQoKIyBzdGFuZGFyZGl6ZSBsdW0gY29sbmFtZXMKY29sbmFtZXMobHVtKSA8LSBnc3ViKCJfIiwiLiIsdG9sb3dlcihjb2xuYW1lcyhsdW0pKSkKaWYoIWFueShncmVwbCgiZHJ1ZzEiLGNvbG5hbWVzKGx1bSkpKSkgY29sbmFtZXMobHVtKSA8LSBnc3ViKCJkcnVnIiwiZHJ1ZzEiLGNvbG5hbWVzKGx1bSkpCmNvbG5hbWVzKGx1bSlbY29sbmFtZXMobHVtKT09InRvdGhvdXIubHVtIl0gPC0gInRpbWUiCmNvbG5hbWVzKGx1bSlbY29sbmFtZXMobHVtKT09ImRhdGV0aW1lLmx1bSJdIDwtICJsdW0udGltZSIKCiMgdWxmc3QgPSB1bmlxdWUgbHVtIGZpbGUgc2F2ZSB0aW1lcwp1bGZzdCA8LSB1bmlxdWUobHVtJGx1bS50aW1lKQoKIyBwbGF0ZSBuYW1lIChiYXJjb2RlIGZyb20gTW9tZW50dW0gbG9nKQojIHBuIDwtIG1sW2Nsb3Nlc3RUaW1lKHVuaXF1ZShjYyRpbWFnZS50aW1lKSxtbFtncmVwbCgnUkZQJyxtbCRpZCksJ3N0YXJ0LnRpbWUucmZtdCddKSwnYmFyY29kZSddCiMgcGxhdGUgbmFtZSAoZnJvbSBsdW0pCnBuIDwtIGx1bVtjbG9zZXN0VGltZSh1bmlxdWUoY2MkaW1hZ2UudGltZSksdW5pcXVlKGx1bSRsdW0udGltZSksZGlyZWN0aW9uPSJiZWZvcmUiKSwncGxhdGUubmFtZSddCm5hbWVzKHBuKSA8LSB1bmlxdWUoY2MkaW1hZ2UudGltZSkKCiMgYWRkIHBsYXRlLm5hbWUgKGJhcmNvZGUpIHRvIGNjCmNjJHBsYXRlLm5hbWUgPC0gcG5bbWF0Y2goY2MkaW1hZ2UudGltZSxuYW1lcyhwbikpXQoKIyBtdXN0IHN1bSBjZWxsIGNvdW50cyBhbmQgY2gyX3BvcyBwZXIgdWlkMiAodW5pcXVlIGZvciBlYWNoIHRpbWUgcG9pbnQpIApjYyR1aWQgPC0gcGFzdGUoY2MkcGxhdGUuaWQsY2Mkd2VsbCxzZXA9Il8iKQpjYyR1aWQyIDwtIHBhc3RlKGNjJHVpZCxjYyRwbGF0ZS5pZCxzZXA9Il8iKQpjY190ZW1wIDwtIGNjWyFkdXBsaWNhdGVkKGNjJHVpZDIpLF0KY2NfdGVtcCRjZWxsLmNvdW50IDwtIHNhcHBseSh1bmlxdWUoY2MkdWlkMiksIGZ1bmN0aW9uKHgpIHN1bShjY1tjYyR1aWQyPT14LCdjZWxsLmNvdW50J10pKQpjY190ZW1wJGNoMi5wb3MgPC0gc2FwcGx5KHVuaXF1ZShjYyR1aWQyKSwgZnVuY3Rpb24oeCkgc3VtKGNjW2NjJHVpZDI9PXgsJ2NoMi5wb3MnXSkpCgojIHRpbWUgbG9va3VwIHRhYmxlOiBjYyBpbWFnZSB0aW1lcyBhcyBuYW1lcywgbHVtIGZpbGUgc2F2ZSB0aW1lcyBhcyB2YWx1ZXMKIyB0aW1lX2x1dCA8LSBjbG9zZXN0VGltZSh1bmlxdWUoY2NfdGVtcCRpbWFnZS50aW1lKSx1bGZzdCxkaXJlY3Rpb249ImJlZm9yZSIsb3V0PSJ0aW1lIikKCiMgQWRkIGx1bWluZXNjZW5jZSByZWFkIHRpbWVzOyBub3RlIHRoYXQgdGhlIGZpcnN0IGx1bWluZXNjZW5jZSByZWFkIGlzIG1pc3NpbmcKIyBjY190ZW1wJGx1bS50aW1lIDwtIHRpbWVfbHV0W2NjX3RlbXAkaW1hZ2UudGltZV0Ka2VlcF9jb2xzIDwtIGMoInBsYXRlLm5hbWUiLCJpbWFnZS50aW1lIiwibHVtLnRpbWUiLCJ3ZWxsIiwiY2VsbC5jb3VudCIsImNoMi5wb3MiKQojIHJlbW92ZSB1bm5lZWRlZCBjb2x1bW5zIGZyb20gY2NfdGVtcApjY190ZW1wIDwtIGNjX3RlbXBbLGNvbG5hbWVzKGNjX3RlbXApICVpbiUga2VlcF9jb2xzXQojIGZpcnN0IHBsYXRlIGRpZCBub3QgZ2V0IGx1bWluZXNjZW5jZSByZWFkOyBtdXN0IHB1bGwgaW5mbyBmcm9tIE1vbWVudHVtIGxvZ3Mgb3IgcmVtb3ZlCmNjX3RlbXAgPC0gY2NfdGVtcFshaXMubmEoY2NfdGVtcCRwbGF0ZS5uYW1lKSxdCiMgY2NfdGVtcCR1aWQgPC0gcGFzdGUoY2NfdGVtcCRwbGF0ZS5uYW1lLGNjX3RlbXAkd2VsbCxjY190ZW1wJGx1bS50aW1lLCBzZXA9Il8iKQoKbHVtJHVpZCA8LSBwYXN0ZShsdW0kbHVtLnRpbWUsIGx1bSR3ZWxsLCBzZXA9Il8iKQpsdW0kcGlkIDwtIHBhc3RlKGx1bSRwbGF0ZS5uYW1lLCBsdW0kbHVtLnRpbWUsIHNlcD0iXyIpCgojIGtlZXAgb25seSB1bmlxdWUgdGltZXMgZm9yIGVhY2ggbHVtaW5lc2NlbmNlIHJlYWQKbHVtX3RpbWVzIDwtIGx1bVshZHVwbGljYXRlZChsdW0kcGlkKSxdCmx1bV90aW1lcyRwbGF0ZS5uYW1lIDwtIGZhY3RvcihsdW1fdGltZXMkcGxhdGUubmFtZSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHM9dW5pcXVlKGx1bV90aW1lcyRwbGF0ZS5uYW1lKVtvcmRlcihsdW1fdGltZXNbIWR1cGxpY2F0ZWQobHVtX3RpbWVzJHBsYXRlLm5hbWUpLCJ0aW1lIl0pXSkKbHVtX3RpbWVzIDwtIGx1bV90aW1lc1tvcmRlcihsdW1fdGltZXMkcGxhdGUubmFtZSxsdW1fdGltZXMkdGltZSksXQpgYGAKCiMjIE5PVEU6IHBsYXRlIG5hbWVzIGFuZCB0aW1lcyBkbyBub3QgbWF0Y2ggYmV0d2VlbiBpbWFnaW5nIGFuZCBsdW0KVHJ5IHRvIHVzZSBtb21lbnR1bSBsb2cgdG8gcmVjb25jaWxlLiBUaGlzIGNvZGUgbGlrZWx5IG5vdCBuZWNlc3Nhcnkgc2luY2UgYWxyZWFkeSBoYXZlIG1hdGNoZWQgdGltZXMgaW4gYGNjX3RlbXBgKQpgYGB7ciBUaW1lIG1hdGNoaW5nIHBhcnQgMn0KbWwkc3RhcnQudGltZS5yZm10IDwtIHN0cnB0aW1lKG1sJHN0YXJ0LnRpbWUsIGZvcm1hdD0iJW0vJWQvJVkgJXIiKQptbCRlbmQudGltZS5yZm10IDwtIHN0cnB0aW1lKG1sJGVuZC50aW1lLCBmb3JtYXQ9IiVtLyVkLyVZICVyIikKbWwkaW1hZ2UudGltZSA8LSB1bmlxdWUoY2MkaW1hZ2UudGltZSlbY2xvc2VzdFRpbWUobWwkc3RhcnQudGltZS5yZm10LHVuaXF1ZShjYyRpbWFnZS50aW1lKSldCgojIGNvcHkgaW1hZ2UudGltZSBmcm9tIHN1Y2Nlc3NpdmUgcm93Cm1sW2dyZXAoIkx1bSIsbWwkaWQpLCJpbWFnZS50aW1lIl0gPC0gbWxbZ3JlcCgiTHVtIixtbCRpZCkrMSwiaW1hZ2UudGltZSJdCgptbCRsdW0udGltZSA8LSB1bmlxdWUobHVtX3RpbWVzJGx1bS50aW1lKVtjbG9zZXN0VGltZShtbCRlbmQudGltZS5yZm10LHVuaXF1ZShsdW1fdGltZXMkbHVtLnRpbWUpKV0KIyBjb3B5IGx1bS50aW1lIGZyb20gcHJlY2VkaW5nIHJvdwptbFtncmVwKCJSRlAiLG1sJGlkKSwibHVtLnRpbWUiXSA8LSBtbFtncmVwKCJSRlAiLG1sJGlkKS0xLCJsdW0udGltZSJdCmBgYAoKCgpgYGB7ciBBZGQgY2VsbCBjb3VudHMgdG8gbHVtIGRhdGF9CmNjX3RlbXAkbHVtLnRpbWUgPC0gbWxbbWF0Y2goY2NfdGVtcCRpbWFnZS50aW1lLG1sJGltYWdlLnRpbWUpLCJsdW0udGltZSJdCmNjX3RlbXAkdWlkIDwtIHBhc3RlKGNjX3RlbXAkbHVtLnRpbWUsIGNjX3RlbXAkd2VsbCwgc2VwPSJfIikKY2NfdGVtcCA8LSBjY190ZW1wW21hdGNoKGx1bSR1aWQsY2NfdGVtcCR1aWQpLF0Kcm93bmFtZXMoY2NfdGVtcCkgPC0gTlVMTAoKbHVtMiA8LSBjYmluZChsdW0sIGNjX3RlbXBbLGMoJ2NlbGwuY291bnQnLCdjaDIucG9zJywnaW1hZ2UudGltZScpXSkKYGBgCgojIyBOT1RFOiBmaXJzdCBsdW1pbmVzY2VuY2UgcmVhZGluZyBpcyBtaXNzaW5nCgojIyMjIE5PVEU6IGluIHRoaXMgZGF0YXNldCwgbHVtaW5lc2NlbmNlIHZhbHVlcyBiZWxvdyB+MjAwMCBpbmRpc3Rpbmd1aXNoYWJsZSBmcm9tIGJhY2tncm91bmQKCgojIyMjIFNlcGFyYXRlIGRhdGEgYnkgYmFyY29kZSAoY2VsbCBsaW5lKQpQbGF0ZSBiYXJjb2RlcyB3ZXJlIHNhdmVkIGFzIGBwbGF0ZS5uYW1lYC4gRWFjaCBiYXJjb2RlIGlzIGFzc29jaWF0ZWQgd2l0aCBhIGRpZmZlcmVudCBjZWxsIGxpbmUuCmBgYHtyfQpieV9wbGF0ZSA8LSBsYXBwbHkodW5pcXVlKGx1bTIkcGxhdGUubmFtZSksIGZ1bmN0aW9uKHBuKSBsdW0yW2x1bTIkcGxhdGUubmFtZT09cG4sXSkKbmFtZXMoYnlfcGxhdGUpIDwtIHVuaXF1ZShsdW0yJHBsYXRlLm5hbWUpCmBgYAoKCmBgYHtyIFBsb3QgYWxsIGNvbnRyb2wgd2VsbHMsIGZpZy5oZWlnaHQ9MTEsIGZpZy53aWR0aD04LjV9CnBhcihtZnJvdz1jKDQsMykpCmZvciAoYmFyY29kZSBpbiBuYW1lcyhieV9wbGF0ZSkpIHsKICAgIGNvbnQgPC0gYnlfcGxhdGVbW2JhcmNvZGVdXVtieV9wbGF0ZVtbYmFyY29kZV1dJGRydWcxLmNvbmM9PTAsXQogICAgY29udCA8LSBjb250W29yZGVyKGNvbnQkd2VsbCxjb250JHRpbWUpLF0KICAgIGlmKGFueShncmVwbCgiRE1TNTMiLGNvbnQkY2VsbC5saW5lKSkpIGNvbnQgPC0gY29udFtjb250JHRpbWUgPD0gNDgsXQogICAgY29udCA8LSBjb250W2NvbnQkdGltZSA8PSA5NixdCiAgICBjbCA8LSB1bmlxdWUoY29udCRjZWxsLmxpbmUpCiAgICAjZG8uY2FsbChwbG90R0MsIGdldEdDYXJncyhjb250W2NvbnQkd2VsbCAlaW4lIGMoJ0IxMScsJ0MxMScsJ0QxMScpLF0sIAogICAgIyAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0LmNvbCA9IGMoInRpbWUiLCAiY2VsbC5jb3VudCIsICJ3ZWxsIikpKQogICAgaW52aXNpYmxlKGRvLmNhbGwocGxvdEdDLCBhcHBlbmQoZ2V0R0NhcmdzKGNvbnQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRhdC5jb2wgPSBjKCJ0aW1lIiwgImNlbGwuY291bnQiLCAid2VsbCIpKSxsaXN0KG1haW49cGFzdGUoY2wsImNlbGwgY291bnQgZG91YmxpbmdzIikpKSkpCiAgICBpbnZpc2libGUoZG8uY2FsbChwbG90R0MsIGFwcGVuZChnZXRHQ2FyZ3MoY29udCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0LmNvbCA9IGMoInRpbWUiLCAicmx1IiwgIndlbGwiKSksbGlzdChtYWluPXBhc3RlKGNsLCJsdW0gZG91YmxpbmdzIikpKSkpCiAgICBpbnZpc2libGUoZG8uY2FsbChwbG90R0MsIGFwcGVuZChnZXRHQ2FyZ3MoY29udCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGF0LmNvbCA9IGMoInRpbWUiLCAicmx1IiwgIndlbGwiKSksbGlzdChtYWluPXBhc3RlKGNsLCJsdW0iKSxjb3VudC50eXBlID0gImxvZyIpKSkpICAgIAp9CgpgYGAKCiMjIyBDbGF5dG9uJ3MgaGV1cmlzdGljCkNsYXl0b24gV2FuZGlzaGluIHN0YXRlcyB0aGF0IHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBsdW1pbnNjZW5jZSBhbmQgY2VsbCBjb3VudCBjYW4gYmUgZXhwbGFpbmVkIGJ5IHRoZSBmb2xsb3dpbmcgbWF0aGVtYXRpY2FsIG1vZGVsOgoKYDEgPSBkL2R4KGxvZzIoSipMdW0pKWAgIAoKCmBgYHtyfQpteV9sdW0gPC0gbHVtMltsdW0yJHRpbWUgPD0gOTYsYygnY2VsbC5saW5lJywndGltZScsJ3JsdScpXQpkbXM1MyA8LSBteV9sdW1bbXlfbHVtJGNlbGwubGluZT09IkRNUzUzIiAmIG15X2x1bSR0aW1lIDw9IDQ4LF0KbXlfbHVtIDwtIHJiaW5kKG15X2x1bVtteV9sdW0kY2VsbC5saW5lIT0iRE1TNTMiLF0sZG1zNTMpCgpqIDwtIGMoSDEwNDg9MTgsSDg0MT0xNCxETVM1Mz0xMikKYGBgCgpgYGB7ciBmaWcuaGVpZ2h0PTMsIGZpZy53aWR0aD04LjV9CnBhcihtZnJvdz1jKDEsMykpCmludmlzaWJsZShsYXBwbHkobmFtZXMoaiksIGZ1bmN0aW9uKG4pIHsKICAgIGRhdCA8LSBteV9sdW1bbXlfbHVtJGNlbGwubGluZT09bixdCiAgICBwbG90KGRhdCR0aW1lLGxvZzIoZGF0JHJsdSkqZGF0JHRpbWUvaltuXSkKfQopKQpgYGAKCg==